﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web.Services.Protocols;
using System.Xml;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Metadata;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;

namespace CRMAssocManyToMany
{
    class Program
    {
        private const int OperationType = 0;
        private const int ServerUrl = 1;
        private const int OrgName = 2;
        private const int RelationshipName = 3;
        private const int EntityName1 = 4;
        private const int EntityName2 = 5;
        private const int Filename = 6;

        static void Main(string[] args)
        {

            int recordCounter = 0;
            int errorCount = 0;

            try
            {
                if (args.Length != 7)
                {
                    Console.WriteLine("Invalid Command line Arguements");
                    Console.WriteLine();
                    Console.WriteLine("Usage  : CRMAssocManyToMany.exe <import|export> <server url> <orgname> <N:N relationshipname> <EntityName1> <EntityName2> <Filename>");
                    Console.WriteLine();
                    Console.WriteLine("Example: CRMAssocManyToMany.exe import \"http:/CRM:5555\" \"MyOrg\" \"contactleads_association\" \"lead\" \"contact\" \"C:\\Import.txt\"");
                    Console.WriteLine();
                }
                else
                {
                    for(int i = 0; i < args.Length; i++)
                    {
                        args[i].Replace("\"", "");
                    }

                    CrmService service = GetCrmService(args);
                    MetadataService metadataService = GetMetadataService(args);

                    if (args[OperationType].ToLower() == "import")
                    {
                        Import(args, ref recordCounter, ref errorCount, service);
                    }
                    else if (args[OperationType].ToLower() == "export")
                    {
                        Export(args, ref recordCounter, ref errorCount, service, metadataService);
                    }
                    else
                    {
                        throw new Exception("'import' or 'export' are the only valid values for the first parameter");
                    }
                    Console.WriteLine("{0} Associations Processed with {1} Errors", recordCounter, errorCount);
                }
            }
            catch (SoapException sex)
            {
                Console.WriteLine(sex.Detail.InnerText);
                Console.WriteLine(sex.Message);
                if (sex.InnerException != null)
                    Console.WriteLine(sex.InnerException.Message);
                Console.WriteLine(sex.StackTrace);
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR");
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.Message);
                Console.WriteLine(ex.StackTrace);
            }
            Console.WriteLine("Press any key to continue");
            Console.ReadKey();
        }

        private static MetadataService GetMetadataService(string[] args)
        {
            MetadataService metadataService = new MetadataService();
            metadataService.Credentials = System.Net.CredentialCache.DefaultCredentials;
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.OrganizationName = args[OrgName];
            metadataService.Url = String.Format(Properties.Settings.Default.MetadataServicePath, args[ServerUrl]);
            metadataService.CrmAuthenticationTokenValue = token;
            metadataService.PreAuthenticate = true;
            return metadataService;
        }

        private static CrmService GetCrmService(string[] args)
        {
            CrmService service = new CrmService();
            service.Credentials = System.Net.CredentialCache.DefaultCredentials;
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.OrganizationName = args[OrgName];
            service.CrmAuthenticationTokenValue = token;
            service.Url = String.Format(Properties.Settings.Default.CrmServicePath, args[ServerUrl]);
            return service;
        }

        private static void Import(string[] args, ref int recordCounter, ref int errorCount, CrmService crmService)
        {
            using (StreamReader sr = new StreamReader(args[Filename]))
            {
                string inputLine = sr.ReadLine();

                while (inputLine != null)
                {
                    string[] ids = inputLine.Split(new char[] { ',' });

                    if (ids.Length == 2)
                    {                        
                        
                        AssociateEntitiesRequest request = new AssociateEntitiesRequest();

                        request.Moniker1 = new Moniker();
                        request.Moniker1.Id = new Guid(ids[0].Replace("{", "").Replace("}", ""));
                        request.Moniker1.Name = args[EntityName1];

                        request.Moniker2 = new Moniker();
                        request.Moniker2.Id = new Guid(ids[1].Replace("{", "").Replace("}", ""));
                        request.Moniker2.Name = args[EntityName2];

                        request.RelationshipName = args[RelationshipName];

                        try
                        {
                            crmService.Execute(request);
                        }
                        catch (SoapException sex)
                        {
                            Console.WriteLine("Error Line " + (recordCounter + 1).ToString() + ", Error Message: " + sex.Detail.InnerText);
                            errorCount++;
                        }

                        recordCounter++;

                        inputLine = sr.ReadLine();
                    }
                }
            }
        }

        private static void Export(string[] args, ref int recordCounter, ref int errorCount, CrmService crmService, MetadataService metadataService)
        {
            RetrieveEntityRequest request = new RetrieveEntityRequest()
            {
                EntityItems = EntityItems.IncludeAttributes | EntityItems.IncludeRelationships,
                RetrieveAsIfPublished = true,
            };

            // get the metadata for the intersect, and the related entities and make sure they exist
            request.LogicalName = args[RelationshipName];

            EntityMetadata relationshipMetadata = null;
            EntityMetadata entity1Metadata = null;
            EntityMetadata entity2Metadata = null;

            request.LogicalName = args[EntityName1];

            try
            {
                RetrieveEntityResponse serviceResponse = (RetrieveEntityResponse)metadataService.Execute(request);
                entity1Metadata = serviceResponse.EntityMetadata;
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Could not retrieve metadata for entity '{0}'", args[EntityName1]), ex);
            }

            request.LogicalName = args[EntityName2];

            try
            {
                RetrieveEntityResponse serviceResponse = (RetrieveEntityResponse)metadataService.Execute(request);
                entity1Metadata = serviceResponse.EntityMetadata;
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Could not retrieve metadata for entity '{0}'", args[EntityName2]), ex);
            }                        

            try
            {
                RetrieveEntityResponse serviceResponse = (RetrieveEntityResponse)metadataService.Execute(request);
                relationshipMetadata = serviceResponse.EntityMetadata;
            }
            catch (Exception ex)
            {
                throw new Exception(String.Format("Could not retrieve metadata for N:N Relationship '{0}'", args[RelationshipName]), ex);
            }                        

            ManyToManyMetadata intersectMetadata = entity1Metadata.ManyToManyRelationships
                .Where(r => r.SchemaName == args[RelationshipName] &&
                       (r.Entity1LogicalName == args[EntityName1] || r.Entity1LogicalName == args[EntityName2]) &&
                       (r.Entity2LogicalName == args[EntityName1] || r.Entity2LogicalName == args[EntityName2])).FirstOrDefault();

            if (intersectMetadata == null)
                throw new Exception(string.Format("{0} is not a validation N:N relationship between {1} and {2}", args[RelationshipName], args[EntityName1], args[EntityName2]));

            using (StreamWriter sr = new StreamWriter(args[Filename]))
            {
                // Assemble FetchXML to query the intersection entity directly
                string fetchXml = "<fetch mapping='logical'> <entity name='" + intersectMetadata.IntersectEntityName + "'>"
                + "<all-attributes />"
                + "</entity>"
                + "</fetch>";

                // Perform the query
                string strResult = crmService.Fetch(fetchXml);

                // Prepare the results
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(strResult);

                // Scan for results
                XmlNodeList nodeList = xmlDoc.SelectNodes("resultset/result");

                foreach (XmlNode node in nodeList)
                {                    
                    if (intersectMetadata.Entity1LogicalName == args[EntityName1])
                        sr.WriteLine(String.Format("{0},{1}",
                            node.SelectSingleNode(intersectMetadata.Entity1IntersectAttribute).InnerText,
                            node.SelectSingleNode(intersectMetadata.Entity2IntersectAttribute).InnerText));
                    else
                        sr.WriteLine(String.Format("{0},{1}",
                            node.SelectSingleNode(intersectMetadata.Entity2IntersectAttribute).InnerText,
                            node.SelectSingleNode(intersectMetadata.Entity1IntersectAttribute).InnerText));

                    recordCounter++;
                }
                sr.Close();
            }
        }
    }
}